package com.google.code.jdbclogger;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;

import com.google.code.jdbclogger.logging.Logger;
import com.google.code.jdbclogger.statement.PreparedStatementFactory;
import com.google.code.jdbclogger.statement.StatementFactory;

/**
 * @author Andrey Vityuk
 */
public final class ConnectionFactory {
    private static Constructor<Connection> connectionConstructor;
    static {
	@SuppressWarnings("unchecked")
	Class<Connection> connectionClass = (Class<Connection>) Proxy.getProxyClass(ConnectionFactory.class
		.getClassLoader(), new Class[] { Connection.class });
	try {
	    connectionConstructor = connectionClass.getConstructor(InvocationHandler.class);
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    public static Connection create(Connection connection, Logger logger) {
	try {
	    return connectionConstructor.newInstance(new ConnectionInvocationHandler(connection, logger));
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    private static class ConnectionInvocationHandler implements InvocationHandler {
	private final Connection connection;
	private final Logger logger;

	public ConnectionInvocationHandler(Connection connection, Logger logger) {
	    this.connection = connection;
	    this.logger = logger;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	    Object result = invokeMethod(method, args);

	    final String methodName = method.getName();
	    if ("createStatement".equals(methodName)) {
		Statement statement = (Statement) result;

		return StatementFactory.create(statement, logger);
	    }
	    if (hasArgs(args) && "prepareStatement".equals(methodName)) {
		PreparedStatement preparedStatement = (PreparedStatement) result;
		String sql = (String) args[0];

		return PreparedStatementFactory.create(preparedStatement, logger, sql);
	    }

	    return result;
	}

	private Object invokeMethod(Method method, Object[] args) throws Throwable {
	    try {
		return method.invoke(connection, args);
	    } catch (InvocationTargetException e) {
		Throwable underlineException = e.getCause();
		throw underlineException;
	    }
	}

	private static boolean hasArgs(Object[] args) {
	    return args != null;
	}
    }
}
